﻿// ChaosKitInterface code
// See http://ChaosKitInterface.codeplex.com for license and usage terms.
// Author: Scientio LLC. http://www.scientio.com
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.IO;
using System.Xml.Linq;
using System.Globalization;


namespace ChaosKitInterface
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {

        /// <summary>
        /// This is the web service connection
        /// </summary>
        ChaosKitService.ChaosKitSoapClient serv = new ChaosKitInterface.ChaosKitService.ChaosKitSoapClient();
        /// <summary>
        /// This identifies the user's account for charging and acess purposes 
        /// </summary>
        string WebServiceID = string.Empty;
        /// <summary>
        /// The id of the series currently being processed/modified
        /// </summary>
        string CurrentSeries = string.Empty;

        ChaosKitService.SeriesInfo currentSeriesInfo = null;
        /// <summary>
        /// Constructor
        /// </summary>
        public Window1()
        {
            InitializeComponent();
            if (!string.IsNullOrEmpty(Properties.Settings.Default.WebServiceID))
            {
                WebServiceID = Properties.Settings.Default.WebServiceID;
                SetWebServiceIDBox.Text = WebServiceID;
                StatusBarWebServiceIDLabel.Content = "Web service ID: Set";
                //A well known bug in Microsoft's Azure means that developers cannot get the web service metadata 
                //direct from the web service. The solution is to use a dummy version of the web service
                //in development and then redirect the web service to the real connection at run time.
                serv.Endpoint.Address = new System.ServiceModel.EndpointAddress(new Uri("Http://www.scientio.com/ChaosKit.asmx"));
                EnableTabs();
                PopulateSeriesList();
            }
            else 
            {
                tabServiceID.IsSelected = true;
                EnableTabs();
            }
        }

        /// <summary>
        /// Fill the series list with the names of the series available to this user
        /// </summary>
        private void PopulateSeriesList()
        {
            try
            {
                ChaosKitService.SeriesInfo[] infos = serv.GetExistingSeries(WebServiceID);
                List<SeriesInfoWrapper> series = new List<SeriesInfoWrapper>();
                foreach (ChaosKitService.SeriesInfo info in infos)
                {
                    series.Add(new SeriesInfoWrapper(info));
                }
                SelectSeriesListBox.ItemsSource = series;
                if (series.Count > 0)
                    SelectSeriesListBox.SelectedIndex = 0;
            }
            catch(Exception ex)
            {
                MessageBox.Show("Could not connect to remote site. Error: " + ex.Message, "Web Service Error", MessageBoxButton.OK);
            }
        }

        /// <summary>
        /// Save the WebServiceID
        /// </summary>
        /// <param name="e"></param>
        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            Properties.Settings.Default.WebServiceID = WebServiceID;
            Properties.Settings.Default.Save();
            base.OnClosing(e);
        }

        private void tabControl1_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {

        }
        /// <summary>
        /// Create a new time series definition
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CreateSeriesButton_Click(object sender, RoutedEventArgs e)
        {
            //First check that the input values are correct.
            TimeSpan samplePeriod = TimeSpan.MaxValue;
            TimeSpan deadPeriod = TimeSpan.MaxValue;
            try
            {
                samplePeriod = TimeSpan.Parse(CreateSeriesSampleTimeBox.Text);
            }
            catch
            {
                MessageBox.Show("Error in sample time. Cannot convert to Date/Time.", "Format Error", MessageBoxButton.OK);
                return;
            }

            try
            {
                if (!string.IsNullOrEmpty(CreateSeriesDeadPeriodBox.Text))
                {
                    deadPeriod = TimeSpan.Parse(CreateSeriesDeadPeriodBox.Text);
                }
            }
            catch
            {
                MessageBox.Show("Error in dead period. Cannot convert to Date/Time.", "Format Error", MessageBoxButton.OK);
                return;
            }
            if (string.IsNullOrEmpty(CreateSeriesNameBox.Text))
            {
                MessageBox.Show("Please supply a name for the series.", "Format Error", MessageBoxButton.OK);
                return;
            }
            currentSeriesInfo = new ChaosKitInterface.ChaosKitService.SeriesInfo();
            currentSeriesInfo.Name = CreateSeriesNameBox.Text;
            currentSeriesInfo.SampleTime = CreateSeriesSampleTimeBox.Text;
            currentSeriesInfo.DeadPeriod = CreateSeriesDeadPeriodBox.Text;
            currentSeriesInfo.Differencing = CreateSeriesDifferencingBox.IsChecked ?? false;
            currentSeriesInfo.Interpolation = CreateSeriesInterpolationBox.IsChecked ?? false ? "Interpolate" : "none";

            //Note that TimeSpans must be in W3C format.
            currentSeriesInfo.SeriesId = serv.CreateTimeSeries(WebServiceID, 
                string.Empty, XmlConvert.ToString(samplePeriod),
                currentSeriesInfo.Differencing,
                XmlConvert.ToString(deadPeriod), 
                currentSeriesInfo.Interpolation, 
                currentSeriesInfo.Name);
            PopulateSeriesList();
            CurrentSeries = currentSeriesInfo.SeriesId;
            StatusBarSeriesNameLabel.Content = "Current series name: " + currentSeriesInfo.Name;
            StatusBarSeriesIDLabel.Content = "Current Series ID: " + CurrentSeries;
            EnableTabs();
        }

        /// <summary>
        /// Select one of the existing series
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SelectSeriesSelectButton_Click(object sender, RoutedEventArgs e)
        {
            SeriesInfoWrapper lbi = SelectSeriesListBox.SelectedItem as SeriesInfoWrapper;
            if (lbi == null)
                return;
            CurrentSeries = lbi.seriesId;
            StatusBarSeriesNameLabel.Content = "Current series name: " + lbi.seriesInfo.Name;
            StatusBarSeriesIDLabel.Content = "Current Series ID: " + CurrentSeries;
            SelectSeriesSeriesNameBox.Text = lbi.seriesInfo.Name;
            SelectSeriesSampleTimeBox.Text = lbi.seriesInfo.SampleTime;
            SelectSeriesDeadPeriodBox.Text = lbi.seriesInfo.DeadPeriod;
            SelectSeriesDifferencingBox.IsChecked = lbi.seriesInfo.Differencing;
            SelectSeriesInterpolationBox.IsChecked = lbi.seriesInfo.Interpolation == "none" ? false : true;
            currentSeriesInfo = lbi.seriesInfo;
            EnableTabs();
        }

        /// <summary>
        /// Delete the current selected series
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SelectSeriesDeleteButton_Click(object sender, RoutedEventArgs e)
        {
            if (MessageBox.Show("Are you Sure?", "Delete Series", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
            {
                serv.DeleteTimeSeries(WebServiceID, CurrentSeries);
                CurrentSeries = string.Empty;
                currentSeriesInfo = null;
                PopulateSeriesList();
                EnableTabs();
            }
        }

        /// <summary>
        /// Update the properties of the selected series.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SelectSeriesUpdateBox_Click(object sender, RoutedEventArgs e)
        {
            //First check that the input values are correct.
            TimeSpan samplePeriod = TimeSpan.MaxValue;
            TimeSpan deadPeriod = TimeSpan.MaxValue;
            try
            {
                samplePeriod = TimeSpan.Parse(SelectSeriesSampleTimeBox.Text);
            }
            catch
            {
                MessageBox.Show("Error in sample time. Cannot convert to Date/Time.", "Format Error", MessageBoxButton.OK);
                return;
            }

            try
            {
                if (!string.IsNullOrEmpty(SelectSeriesDeadPeriodBox.Text))
                {
                    deadPeriod = TimeSpan.Parse(SelectSeriesDeadPeriodBox.Text);
                }
            }
            catch
            {
                MessageBox.Show("Error in dead period. Cannot convert to Date/Time.", "Format Error", MessageBoxButton.OK);
                return;
            }
            if (string.IsNullOrEmpty(SelectSeriesSeriesNameBox.Text))
            {
                MessageBox.Show("Please supply a name for the series.", "Format Error", MessageBoxButton.OK);
                return;
            }
            currentSeriesInfo.Name = SelectSeriesSeriesNameBox.Text;
            currentSeriesInfo.SampleTime = SelectSeriesSampleTimeBox.Text;
            currentSeriesInfo.DeadPeriod = SelectSeriesDeadPeriodBox.Text;
            currentSeriesInfo.Differencing = SelectSeriesDifferencingBox.IsChecked ?? false;
            currentSeriesInfo.Interpolation = SelectSeriesInterpolationBox.IsChecked ?? false ? "Interpolate" : "none";

            serv.UpdateTimeSeries(WebServiceID, CurrentSeries, string.Empty,
                XmlConvert.ToString(samplePeriod), currentSeriesInfo.Differencing,
                XmlConvert.ToString(deadPeriod), currentSeriesInfo.Interpolation,
                currentSeriesInfo.Name);
        }

        /// <summary>
        /// Analyze the current series and report the measures.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AnalyzeAndPredictAnalyzeButton_Click(object sender, RoutedEventArgs e)
        {
            ChaosKitService.AnalysisResults res = serv.Analyze(WebServiceID, CurrentSeries);
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Embedding dimension: " + res.EmbeddingDimension.ToString());
            sb.AppendLine("Embedding Separation: " + res.EmbeddingSeparation.ToString());
            sb.AppendLine("Fractal dimension: " + res.FractalDimension.ToString());
            sb.AppendLine("Lyapunov exponent: " + res.LyapunovExponent.ToString());
            sb.AppendLine("Hurst exponent: " + res.HurstExponent.ToString());
            AnalyzeAndPredictResultsBox.Text = sb.ToString();
        }

        /// <summary>
        /// Predict beyond the end of the current series
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AnalyzeAndPredictPredictButton_Click(object sender, RoutedEventArgs e)
        {
            int predictions = int.Parse(((ComboBoxItem)AnalyzeAndPredictPredictionsDropDown.SelectedItem).Content.ToString());
            ChaosKitService.PredictionResult[] results =  serv.PredictSimple(WebServiceID, CurrentSeries, predictions);
            StringBuilder sb = new StringBuilder();
            foreach (ChaosKitService.PredictionResult res in results)
            {
                sb.AppendLine("Time: " + res.TimeStamp.ToShortDateString() + " " + res.TimeStamp.ToShortTimeString() + " Value: " +res.Value.ToString());
            }
            AnalyzeAndPredictResultsBox.Text = sb.ToString();
        }

        /// <summary>
        /// Add a new data value to the series
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateAndPredictUpdateButton_Click(object sender, RoutedEventArgs e)
        {
            //check values and dates are valid
            double sampleValue = 0.0;
            try
            {
                sampleValue = Convert.ToDouble(UpdateAndPredictNewValueBox.Text);
            }
            catch
            {
                MessageBox.Show("Error in value. Cannot convert to floating point number.", "Format Error", MessageBoxButton.OK);
                return;
            }
            DateTime sampleTime = DateTime.Now;
            if (UpdateSampleTimeEndPlusRadio.IsChecked ?? false)
            {
                TimeSpan samplePeriod = TimeSpan.Parse(currentSeriesInfo.SampleTime);
                //Get the end of the series from the server and add one sample period
                sampleTime = serv.GetLastSampleTime(WebServiceID, CurrentSeries) + samplePeriod;
            }
            else if (UpdateSampleTimeFixedRadio.IsChecked ?? false)
            {
                try
                {
                    sampleTime = Convert.ToDateTime(UpdateSampleTimeFixedBox.Text);
                }
                catch
                {
                    MessageBox.Show("Error in sample time. Cannot convert to Date/Time.", "Format Error", MessageBoxButton.OK);
                    return;
                }
            }
            serv.AddSample(WebServiceID, CurrentSeries,sampleValue,sampleTime);
        }

        /// <summary>
        /// Predict off the end of the series
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateAndPredictPredictButton_Click(object sender, RoutedEventArgs e)
        {
            int predictions = int.Parse(((ComboBoxItem)UpdateAndPredictPredictionsDropDown.SelectedItem).Content.ToString());
            ChaosKitService.PredictionResult[] results = serv.PredictSimple(WebServiceID, CurrentSeries, predictions);
            StringBuilder sb = new StringBuilder();
            foreach (ChaosKitService.PredictionResult res in results)
            {
                sb.AppendLine("Time: " + res.TimeStamp.ToShortDateString() + " " + res.TimeStamp.ToShortTimeString() + " Value: " + res.Value.ToString());
            }
            UpdateAndPredictResultsBox.Text = sb.ToString();
        }

        /// <summary>
        /// Set up the web service ID
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SetWebServiceIDButton_Click(object sender, RoutedEventArgs e)
        {
            WebServiceID = SetWebServiceIDBox.Text;
            StatusBarWebServiceIDLabel.Content = "Web service ID: Set";
            EnableTabs();
            PopulateSeriesList();
        }

        /// <summary>
        /// Upload CSV data to the web service (converting to XML first)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UploadDataUploadButton_Click(object sender, RoutedEventArgs e)
        {
            //try to convert the csv text into values, and if supplied, times.
            NumberFormatInfo provider = new NumberFormatInfo( );

            provider.NumberDecimalSeparator = ".";
            provider.NumberGroupSeparator = ",";
            provider.NumberGroupSizes = new int[ ] { 3 };

            if (UploadDataRadioValueOnly.IsChecked ?? false)
            {//data just contains values
                int lineCount = 0;
                UploadDataSourceBox.SelectAll();
                StringReader read = new StringReader(UploadDataSourceBox.Selection.Text);
                XElement root = new XElement("samples");
                string line;
                while ((line = read.ReadLine()) != null)
                {
                    string[] elements = line.Split(new char[] { ' ', ',', '\t' });
                    if (elements.Length > 0 && !string.IsNullOrEmpty(elements[0]))
                    {
                        double val = 0.0;
                        try
                        {
                            val = Convert.ToDouble(elements[0], provider);
                        }
                        catch
                        {
                            MessageBox.Show("Error in value line " + lineCount.ToString() + ". Cannot convert to floating point number.", "Format Error", MessageBoxButton.OK);
                            return;
                        }
                        root.Add(new XElement("sample",new XElement("value",new XText(elements[0]))));
                    }
                    lineCount++;
                }
                if (!LineCountCheck(lineCount))
                    return;
                TimeSpan sampleTime = TimeSpan.Parse(currentSeriesInfo.SampleTime);
                int count = serv.AddSampledSeriesData(WebServiceID, CurrentSeries, root.ToString(), @"//sample", "./value/text()", new DateTime(2000, 1, 1, 0, 0, 0), XmlConvert.ToString(sampleTime));
                MessageBox.Show(count.ToString() + " samples uploaded.", "Data upload", MessageBoxButton.OK);
            }
            else
            {//data contains values and times in that order.
                int lineCount = 0;
                UploadDataSourceBox.SelectAll();
                StringReader read = new StringReader(UploadDataSourceBox.Selection.Text);
                XElement root = new XElement("samples");
                string line;
                while ((line = read.ReadLine()) != null)
                {
                    string[] elements = line.Split(new char[] { ' ', ',', '\t' });
                    if (elements.Length > 1)
                    {
                        double val = 0.0;
                        try
                        {
                            val = Convert.ToDouble(elements[0], provider);
                        }
                        catch
                        {
                            MessageBox.Show("Error in value line " + lineCount.ToString() + ". Cannot convert first element to floating point number.", "Format Error", MessageBoxButton.OK);
                            return;
                        }
                        DateTime time = DateTime.Now;
                        try
                        {
                            time = Convert.ToDateTime(elements[1]);
                        }
                        catch
                        {
                            MessageBox.Show("Error in date/time line " + lineCount.ToString() + ". Cannot convert second element to a date/time.", "Format Error", MessageBoxButton.OK);
                            return;
                        }
                        root.Add(new XElement("sample", new XElement("value", new XText(elements[0])),new XElement("time", new XText(elements[1]))));
                    }
                    lineCount++;
                }
                if (!LineCountCheck(lineCount))
                    return;
                serv.AddTimeStampedSeriesData(WebServiceID,CurrentSeries,root.ToString(),@"//sample",@"./value/text()",@"./time/text()");
                int count = serv.GetSeriesLength(WebServiceID, CurrentSeries);
                MessageBox.Show(count.ToString() + " samples uploaded.", "Data upload", MessageBoxButton.OK);
            }
        }

        /// <summary>
        /// Check that the limit is not exceeded
        /// </summary>
        /// <param name="lineCount">number of data points</param>
        /// <returns>true if within bounds</returns>
        /// <remarks>The web service also checks, so disabling this will not increase the amount you can upload!</remarks>
        private bool LineCountCheck(int lineCount)
        {
            if (lineCount > 1000)
            {
                MessageBox.Show("The maximum number of data points for the online version is 1000. You have tried to upload " + lineCount.ToString() + " points.", "Size Error", MessageBoxButton.OK);
                return false;
            }
            return true;
        }

        private void EnableTabs()
        {
            tabCreate.IsEnabled = !string.IsNullOrEmpty(WebServiceID);
            tabSelect.IsEnabled = !string.IsNullOrEmpty(WebServiceID);
            tabUpload.IsEnabled = !string.IsNullOrEmpty(WebServiceID) && !string.IsNullOrEmpty(CurrentSeries);
            tabAnalyze.IsEnabled = !string.IsNullOrEmpty(WebServiceID) && !string.IsNullOrEmpty(CurrentSeries);
            tabUpdate.IsEnabled = !string.IsNullOrEmpty(WebServiceID) && !string.IsNullOrEmpty(CurrentSeries);   
        }
    }
}
